#include <cstdlib>

#include <string>
#include <iostream>
#include <fstream>

#include <getopt.h>

#include "mtf"
#include "bs"
#include "RLE"
#include "bitstream"
#include "Estructurado"
#include "CompresorEstructurado"
#include "DescompresorEstructurado"

using namespace std;

int compress(istream *inFile, ostream *outFile, integer blocksize);
int decompress(istream *inFile, ostream *outFile, integer blocksize);

const integer def_blocksize = 1024;

int main(int argc, char **argv) {
	int arg = 0;
	integer blocksize = def_blocksize;
	bool
		C = 0, c = 0,
		X = 0, x = 0,
		O = 0, o = 0,
		b = 0,
		error = 0;
	istream *inFile = nullptr;
	ostream *outFile = nullptr;
	string
		inFilename,
		outFilename,
		newInFilename,
		newOutFilename;
	while((arg = getopt(argc, argv, "Cc:Xx:Oo:b:")) != -1) {
		switch(arg) {
			case 'C':
				C = 1;
				inFile = &cin;
				outFile = &cout;
				break;
			case 'c':
				c = 1;
				inFilename = optarg;
				break;
			case 'X':
				X = 1;
				inFile = &cin;
				outFile = &cout;
				break;
			case 'x':
				x = 1;
				inFilename = optarg;
				break;
			case 'O':
				O = 1;
				outFile = &cout;
				break;
			case 'o':
				o = 1;
				outFilename = optarg;
				break;
			case 'b':
				b = 1;
				blocksize = atoi(optarg);
				break;
			default:
				error = 1;
		}
	}

	if(((c + C + x + X) != 1)
					or (o and O)
//					or ((x or X) and b)
					or error) {
		cerr << "HELP" << endl;
		exit(EXIT_FAILURE);
	}

	if(c and !(o or O)) {
		newOutFilename = inFilename + ".XX";
		outFilename = newOutFilename; 
	}

	if(x) {
		if(inFilename.compare(inFilename.length() - 3, 3, ".XX")) {
			newInFilename = inFilename + ".XX";
			if(!(o or O)) outFilename = inFilename;
			inFilename = newInFilename;
		} else if(!(o or O)) {
			newOutFilename = inFilename.substr(0, inFilename.length() - 3);
			outFilename = newOutFilename;
		}
	}

	cerr << "in:\t" << inFilename << endl << "out:\t" << outFilename << endl <<
		"C:" << C << "\tc:" << c << endl <<
		"X:" << X << "\tx:" << x << endl <<
		"O:" << O << "\to:" << o << endl;

	if(c or o or x) {
		if(c or x) {
			inFile = new ifstream(inFilename);
		}
		if(!*inFile) {
			cerr << "Bad input file: " << inFilename << endl;
			error = 1;
		} else if(!O) {
			outFile = new ofstream(outFilename);
			if((!outFile) or (!*outFile)) {
				cerr << "Bad output file: " << outFilename << endl;
				error = 1;
			}
		}
	}

	if(!error) ((c or C)?compress:decompress)(inFile, outFile, blocksize);

	if((inFile != &cin) and inFile) delete inFile;
	if((outFile != &cout) and outFile) delete outFile;
	exit(error? EXIT_FAILURE: EXIT_SUCCESS);
}

int compress(istream *inFile, ostream *outFile, integer blocksize) {
	obitstream bitwise(outFile);
	integer s = blocksize;
	integer i = 0;
	integer rle_size = 0;
	uchar block[blocksize],
		  rleblock[3*blocksize/2];
	mxf * the_mtf = new mtf();
	CompresorEstructurado the_compressor(&bitwise);
std::cerr << "BS: " << blocksize << endl;
	while(inFile->good() and (s == blocksize)) {
		inFile->read((char*)block, blocksize);
		s = inFile->gcount();
		i = bs(block, s);
		(*the_mtf)(block, s);
		rle_size = rle(block, rleblock, s);
std::cerr << "I:" << i;
std::cerr << "\tS:" << s;
std::cerr << "\tR:" << rle_size << std::endl;
		the_compressor.comprimirBloque(
						(uchar*)&i, INTEGER_UCHAR);
		the_compressor.comprimirBloque(
						(uchar*)&rle_size, INTEGER_UCHAR);
		the_compressor.comprimirBloque(rleblock, rle_size);
//		the_compressor.comprimirBloque(block, s);
	}
	the_compressor.finalizarArchivo(true);
	bitwise.pad();
	delete the_mtf;
	return 0;
}

int decompress(istream *inFile, ostream *outFile, integer blocksize) {
	ibitstream bitwise(inFile);
	integer s = blocksize;
	integer i = 0;
	integer rle_size = 0;
	uchar block[blocksize],
		  rleblock[3*blocksize/2];
	mxf * the_mff = new mff();
	DescompresorEstructurado the_decompressor(&bitwise);
std::cerr << "BS: " << blocksize << endl;
	while(inFile->good() and (s == blocksize)) {
		the_decompressor.descomprimir(
						(uchar*)&i, INTEGER_UCHAR);
std::cerr << "I:" << i;
		the_decompressor.descomprimir(
						(uchar*)&rle_size, INTEGER_UCHAR);
std::cerr << "\tR:" << rle_size;
		the_decompressor.descomprimir(
						rleblock, rle_size);
		s = derle(rleblock, block, rle_size);
//		s = the_decompressor.descomprimir(block, blocksize);
std::cerr << "\tS:" << s << std::endl;
		(*the_mff)(block, s);
		debs(block, i, s);
		outFile->write((char*)block, s);
	}
	delete the_mff;
	return 0;
}

